/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package Objects;

import GameEngine.GameConstants;
import GameEngine.GameConstants.Effect;
import GameEngine.GameConstants.Graphic;
import GameEngine.GameConstants.Material;
import GameEngine.Group;
import GameEngine.Level;
import GameEngine.Vector;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * New version of GameObject running on center/height/width model.
 * mhfowler: This class is the building block of our game simulation. Every object in a level
 * will extend this class, including the players.
 * @author jh20
 */
public class GameObject extends BasicObject {



    /* reference to your parent */
    Level lev;

    private Vector[] corners; // modifiers to the center to get the corners

    /* this boolean tells level if you have changed this iteration or not */
    boolean changed;

    /* the number of iterations which the object has existed */
    int lifetime;

    /* this is taken into account in collisions, used essentially as an object 'category' */
    protected Material material;

    /* this is an array of groups, such that this object could effect all of the objects in one
     * of these groups at once, we will decide how many groups we will allow an object to keep references
     to.....for now just one*/
    protected Group relations;

    /* this is a list of persistent effects which are currently acting on this object, every call
     * of nextIteration() this object will iterate through these effects and receive them on itself */
    List<PersistentEffect> persistent;

    protected boolean isTangible = true;


    /* default constructor */
    public GameObject(Vector d, Vector cen, Vector s, Graphic g) {
        super(d, cen, s, g);
        exists = true;
        lifetime = 0;
        persistent = new LinkedList<PersistentEffect>();
        changed = true;

        corners = new Vector[4];

        Vector proX = direction.scaleNew(center.projectScalar(direction) + size.getX()/2);
        Vector proY = direction.getOrthog().scaleNew(center.projectScalar(direction.getOrthog()) + size.getY()/2);
        corners[0] = proX.addNew(proY).addNew(center.scaleNew(-1));

        proX = direction.scaleNew(center.projectScalar(direction) - size.getX()/2);
        proY = direction.getOrthog().scaleNew(center.projectScalar(direction.getOrthog()) + size.getY()/2);
        corners[1] = proX.addNew(proY).addNew(center.scaleNew(-1));

        proX = direction.scaleNew(center.projectScalar(direction) + size.getX()/2);
        proY = direction.getOrthog().scaleNew(center.projectScalar(direction.getOrthog()) - size.getY()/2);
        corners[2] = proX.addNew(proY).addNew(center.scaleNew(-1));

        proX = direction.scaleNew(center.projectScalar(direction) - size.getX()/2);
        proY = direction.getOrthog().scaleNew(center.projectScalar(direction.getOrthog()) - size.getY()/2);
        corners[3] = proX.addNew(proY).addNew(center.scaleNew(-1));

        // instantiate some things
        velocity = new Vector(0,0);
        deltaPosition = new Vector(0,0);
        acceleration = new Vector(0,0);
    }


    /** this is the method by which other objects will effect this object. There will be a switch case
     * for each effect. For this abstract object, there will be a default implementation for each effect.
     * Specifiec objects will overwrite this method, but within them their default switch case will
     * be super.receiveEffect(), which will cause these default effects. */
    public void receiveEffect(Effect e, Vector dir, int power) {
        changed = true;
        switch (e) {
            case WALL:
                /*
                Vector getRidVel = velocity.project(dir);
                velocity.add(getRidVel.scaleNew(-1));
                /* velocity should become parallel to wall and slightly decreased */
                /*
                Vector getRidPos = deltaPosition.project(dir);
                deltaPosition.add(getRidPos.scaleNew(-1));
                 */
                break;
            case CURRENT:
                 /* find the projection of your velocity onto the wind */
                 /*
                Vector withCurrent1 = velocity.project(dir);
                /* this is how much velocity in direction of wind needs to be added to equal velocity of wind */
                /*
                Vector difference1 = dir.addNew(withCurrent1.scaleNew(-1.0));
                if (difference1.getX() > 0) {
                    velocity.add(new Vector(difference1.getX(), 0).project(dir));
                }
                if (difference1.getY() > 0) {
                    velocity.add(new Vector(0,difference1.getY()).project(dir));
                }
                 * 
                 */
                float projected = this.velocity.projectScalar(dir);
                if (projected > 0) {
                    float diff = dir.getNorm() - projected;
                    if (diff > 0) {
                        velocity.add(dir.scaleNew(diff));
                    }
                } else {
                    velocity.add(dir);
                }
                break;
            case SLIPPERY:
                /**
                 * Velocity should be increased by a factor horizontally
                 */
                velocity.scale(1, .5);
                break;
            case REVERSE:
                Vector between = direction.addNew(center.scaleNew(-1.0));
                velocity.add(between.scaleNew(-power));
                break;
            case PERSISTENT_SPEED:
                PersistentEffect newE = new PersistentEffect(Effect.SPEED, Effect.SLOW, dir, 10, power);
                persistent.add(newE);
                break;
            case DESTROY:
                exists = false;
                lev.destroyObject(this);
                break;
            case EXPLOSION:
                receiveEffect(Effect.DESTROY, dir, power);
                break;
            case TELEPORT:
                center.add(dir);
                break;
            default:
                System.out.println("effect not implemented.");
        }
    }

    /** this method will be called on both objects in a collision wherever the collision checker detects
     * a collision. By default this method will be made up of a switch case of Material, but particular
     * objects may have more interesting and nefarious collision behavior, including calling receiveEffect
     * on the object they collided with, causing an effect on their group, or causing an effect on themself.
     * @param o
     */

    public void collide(GameObject o) {
        switch (o.material) {
            case WALL:
                /*
                Vector wall;
                Vector hereToWall = o.getCenter().addNew(center.scaleNew(-1));
                 Vector axis1 = new Vector(o.getDir());
                 Vector axis2 = axis1.getOrthog();
                 if (Math.abs(axis1.dotProduct(hereToWall)) < Math.abs(axis2.dotProduct(hereToWall))) {
                     wall = axis2;
                 } else {
                     wall = axis1;
                 }
                 if (hereToWall.projectScalar(wall) > 0) {
                     wall.scale(-1);
                 }
                 System.out.println("wall axis " + wall.toString());
                 // we have vector perpendicular to wall facing towards us
                this.receiveEffect(Effect.WALL, wall, 0);
                 * */
                break;
            case WATER:
                /* you flow with the current */
                this.receiveEffect(Effect.CURRENT, o.velocity, 0);
                break;
            case ICE:
                /**
                 * Increases velocity
                 */
                this.receiveEffect(Effect.SLIPPERY, o.velocity, 0);
                break;
            case WIND:
                /* you flow with the current */
                this.receiveEffect(Effect.CURRENT, o.velocity, 0);
                break;
            case SPIKY:
                // you get destroyed
                this.receiveEffect(Effect.DESTROY, null, 0);
                break;
            case BOMB:
                  // you get destroyed
                this.receiveEffect(Effect.DESTROY, null, 0);
                break;
            default:
                break;
        }
    }

    /**
     * Offsets this object after a collision
     * @param offset
     * @param totalNorm
     */

    public void offset(Vector offset, float totalNorm) {
        if (!(totalNorm <= Float.MIN_VALUE && totalNorm >= -Float.MIN_VALUE)) {
            // if not = 0, offset
            float ratio = velocity.getNorm() / totalNorm;
            move(offset.scaleNew(ratio));
            // if we stopped and needed to move back up, in other words, falling and hitting something below us
            if (offset.getY() < 0 && Math.abs(offset.getY()) >= Math.abs(offset.getX())) {
                // negate vertical velocity
                velocity.scale(1, 0);
            }
        }
    }


    /** this method will be called on every object at the beginning of every iteration. In this method
     * particular objects may do specific things, such as arbitrarily update their own attributes based
     * on some correlation to their lifetime (how long they've existed...eg chompers updating their deltaPosition
     * up or down), or even causing an effect on other objects. But every object must iterate through the
     * list of persistent effects on them and receive them.
     */
    public void nextIteration() {
        applyPersistent();
         // use physics
        doPhysics();
        cancelPersistent();


        if (lev != null) {
            if (center.getX() < -size.getX() || center.getX() > lev.getWidth() + size.getX() || center.getY() > lev.getHeight() + size.getY()) {
                receiveEffect(Effect.DESTROY, null, 0);
            }
        }

    }

    /* applies all persistent effects */
    public void applyPersistent() {
            // apply all persistent effects
        Iterator<PersistentEffect> iter1 = persistent.iterator();
        PersistentEffect p;
        // use effect
        while (iter1.hasNext()) {
            p = iter1.next();
            p.useEffect(this);
        }
    }

    /* undoes all persistent effects */
    public void cancelPersistent() { // cancel effect
             // apply all persistent effects
        Iterator<PersistentEffect> iter1 = persistent.iterator();
        PersistentEffect p;
        // use effect
        while (iter1.hasNext()) {
            p = iter1.next();
            p.cancelEffect(this);
        }
    }

    /* MECHANICS */
     /* this is the amount by which the object will move at the next time step,
     * other objects can directly modify this, and it can be changed by the
     * doPhysics() method */
    protected Vector deltaPosition;


    /* these vectors affect deltaPosition when doPhysics() is called, and which
     * should be called every time step */
    protected Vector velocity;
    protected Vector acceleration;            // eg gravity or anti-gravity


    /* This method updates deltaPosition based on the current values of velocity
     * and acceleration */
    void doPhysics() {
        // check if max speed
        if (Math.abs(velocity.getX()) > GameConstants.MAX_SPEED) {
            velocity.setX(GameConstants.MAX_SPEED * Math.signum(velocity.getX()));
        }
        if (Math.abs(velocity.getY()) > GameConstants.MAX_SPEED) {
            velocity.setY(GameConstants.MAX_SPEED * Math.signum(velocity.getY()));
        }
        deltaPosition.add(velocity);
        velocity.add(acceleration);
    }


    /* this method finds the next movement step based on its current position, its deltaposition
     * and the number of steps inputted to get there, and then moves by that step and also decrements deltaposition accordingly */
    /* returns true if the object moved, false otherwise */
    public boolean moveStep(int num_steps) {
        boolean toReturn = false;
        if (deltaPosition.getNorm()!=0) {
            Vector toMove = deltaPosition.scaleNew(1/num_steps);
            move(toMove);
            deltaPosition.add(toMove.scaleNew(-1.0));
            changed = true;
            toReturn = true;
        }
        return toReturn;
    }

    /**
     * Returns if this object contains a point
     * @param x
     * @param y
     * @return
     */

    public boolean contains(float x, float y) {
        Vector pt = new Vector(x, y);
        Vector orthog = direction.getOrthog();
        float proX = pt.projectScalar(direction);
        float proY = pt.projectScalar(orthog);
        float cenX = center.projectScalar(direction);
        float cenY = center.projectScalar(orthog);
        return (proX >= cenX - size.getX()/2 && proX <= cenX + size.getX()/2 && proY >= cenY - size.getY()/2 && proY <= cenY + size.getY()/2);
    }

    /**
     * Returns a corner  * THIS IS THE ONLY WAY CORNERS SHOULD BE ACCESSED 
     * @param i
     * @param j
     * @return A corner if valid input, null if not
     */
    public Vector getCorner(int i) {
        if (i >= 0 && i <= 3) {
            return center.addNew(corners[i]);
        } else {
            return null;
        }
    }

    
    /**
     * Returns coordinates of all corners
     * @return
     */

    public Vector[] getCorners() {
        Vector[] toReturn = new Vector[4];
        for (int i = 0; i < 4; i++) {
                toReturn[i] = center.addNew(corners[i]);
        }
        return toReturn;
    }

    /* increases lifetime by one */
    public void increaseAge() {
        lifetime++;
    }


    public Vector getAcceleration() {
        return acceleration;
    }

    public void setAcceleration(Vector acceleration) {
        this.acceleration = acceleration;
    }

    public Vector getDeltaPosition() {
        return deltaPosition;
    }

    public void setDeltaPosition(Vector deltaPosition) {
        this.deltaPosition = deltaPosition;
    }

    public Vector getVelocity() {
        return velocity;
    }

    public void setVelocity(Vector velocity) {
        this.velocity = velocity;
    }


    /* GETTERS */

    public int getLifetime() {
        return lifetime;
    }

    public Material getMaterial() {
        return material;
    }

    public List<PersistentEffect> getPersistent() {
        return persistent;
    }

    public Group getRelations() {
        return relations;
    }

    /* setters */

    public void setLifetime(int lifetime) {
        this.lifetime = lifetime;
    }

    public void setMaterial(Material material) {
        this.material = material;
    }

    public void setPersistent(List<PersistentEffect> persistent) {
        this.persistent = persistent;
    }

    public void setRelations(Group relations) {
        this.relations = relations;
    }

    public Level getLev() {
        return lev;
    }

    public void setLev(Level lev) {
        this.lev = lev;
    }

     public boolean isChanged() {
        return changed;
    }

    public void setChanged(boolean b) {
        changed = b;
    }

    public boolean isTangible() {
        return isTangible;
    }




}
